#include "cvmat.h"
#include "StaticAssert.hxx"
#include <QMetaEnum>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptEngine>

QScriptValue ctorQCLImageFormat(QScriptContext *context, QScriptEngine *engine)
{
    Q_UNUSED(context)
    return engine->toScriptValue(QCLImageFormat());
}

QScriptValue ctorVec4f(QScriptContext *context, QScriptEngine *engine)
{
    int nArgs = context->argumentCount();
    if (nArgs > 4)
         return context->throwError("Vec4f(x, y, z, w) takes no more than four arguments");

    QScriptValue obj;

    // Scripts would be able to use either the expression new Vec4(2,3,4,2) or Vec4(2,3,4,2)
    if (context->isCalledAsConstructor())
    {
        // The engine creates this new object automatically before invoking your function. 
        // This means that your native constructor function normally doesn't have to 
        // (and shouldn't) create a new object when it is called as a constructor, 
        // since the engine has already prepared a new object.
         obj = context->thisObject();
     }
     else
     {
         obj = engine->newObject();
         obj.setPrototype(context->callee().property("prototype"));
     }
     
    qsreal n[4] = {0,0,0,0};

    for(int i = 0; i < nArgs; ++i)
    {
        if (!context->argument(i).isNumber())
            return context->throwError(QScriptContext::TypeError, QString::fromLatin1("ctorVec4f(): argument %1 is not a number").arg(i));

        n[i] = context->argument(i).toNumber();
    }

    obj.setProperty("x", n[0]);
    obj.setProperty("y", n[1]);
    obj.setProperty("z", n[2]);
    obj.setProperty("w", n[3]);

    // The constructor function should return an undefined value, QScriptEngine::undefinedValue(),
    // to tell the engine that the this object should be the final result of the new operator. 
    // Alternatively, the function can return the this object itself.
    return obj; // engine->undefinedValue();
}

QScriptValue toStringVec4(QScriptContext *context, QScriptEngine *engine)
{
    qsreal n[4] = {0,0,0,0};

    QScriptValue val = context->thisObject();

    n[0] = val.property("x").toNumber();
    n[1] = val.property("y").toNumber();
    n[2] = val.property("z").toNumber();
    n[3] = val.property("w").toNumber();

    QString result = QString::fromLatin1("Vec4f(x: %1, y: %2, z: %3, w: %4)").arg(n[0]).arg(n[1]).arg(n[2]).arg(n[3]);

    return QScriptValue(engine, result);
}

QScriptValue toScriptValueVec4f(QScriptEngine *engine, const cv::Vec4f &s)
{
    //QScriptValue obj = engine->newObject();

    //obj.setProperty("x", s[0]);
    //obj.setProperty("y", s[1]);
    //obj.setProperty("z", s[2]);
    //obj.setProperty("w", s[3]);

    // Create the object reusing the already registered ctorVec4f function and its properties
    QScriptValue ctor = engine->globalObject().property("Vec4f");
    QScriptValue obj = ctor.call(QScriptValue(), QScriptValueList() << s[0] << s[1] << s[2] << s[3]);

    return obj;
}

void fromScriptValueVec4f(const QScriptValue &obj, cv::Vec4f &s)
{
    //cv::Vec4f *that = qscriptvalue_cast<cv::Vec4f*>(obj);

    s[0] = obj.property("x").toNumber();
    s[1] = obj.property("y").toNumber();
    s[2] = obj.property("z").toNumber();
    s[3] = obj.property("w").toNumber();
}


QCLImageFormatPrototype::QCLImageFormatPrototype(QObject *parent)
: QObject(parent)
{
}

QCLImageFormatPrototype::ChannelOrder QCLImageFormatPrototype::channelOrder() const
{
    cpp_type *that = qscriptvalue_cast<cpp_type*>(thisObject());
    if (that)
        return (ChannelOrder) that->channelOrder();

    return Order_Invalid;
}

QCLImageFormatPrototype::ChannelType QCLImageFormatPrototype::channelType() const
{
    cpp_type *that = qscriptvalue_cast<cpp_type*>(thisObject());
    if (that)
        return (ChannelType) that->channelType();

    return Type_Invalid;
}

QString QCLImageFormatPrototype::enumToString(ChannelType aElement) const
{
    int index = metaObject()->indexOfEnumerator("ChannelType");
    QMetaEnum metaEnum = metaObject()->enumerator(index);
    return metaEnum.valueToKey(aElement);
}

QString QCLImageFormatPrototype::enumToString(ChannelOrder aElement) const
{
    int index = metaObject()->indexOfEnumerator("ChannelOrder");
    QMetaEnum metaEnum = metaObject()->enumerator(index);
    return metaEnum.valueToKey(aElement);
}

bool QCLImageFormatPrototype::isNull() const
{
    cpp_type *that = qscriptvalue_cast<cpp_type*>(thisObject());
    if (!that)
		throw context()->throwError(QScriptContext::TypeError, "isNull(): invalid object");

    return true;
}


QString QCLImageFormatPrototype::toString() const
{
    cpp_type that = qscriptvalue_cast<cpp_type>(thisObject());
    //if (!that)
    //{
    //    context()->throwError(QScriptContext::ReferenceError, "toString() const Invalid type");

    //    return QString();
    //}

    QString result = QString::fromLatin1("QCLImageFormat(channelOrder: %1, channelType: %2)")
        .arg(enumToString((ChannelOrder) that.channelOrder())).arg(enumToString((ChannelType) that.channelType()));

    return result;
}

//Vec4fPrototype::Vec4fPrototype(QObject *parent)
//: QObject(parent)
//{
//}
//
//QScriptValue Vec4fPrototype::qscript_call(float x, float y, float z, float w)
//{
//   //QMessageBox * const iface = new CvMatrix(icon,title,text,buttons,parent,f);
//    return engine()->toScriptValue(cv::Vec4f(x,y,z,w));
//}
//
//Vec4fPrototype::element_type Vec4fPrototype::at(int i) const
//{
//    cpp_type *that = qscriptvalue_cast<cpp_type*>(thisObject());
//    if (that)
//        return (*that)[i];
//
//    context()->throwError(QScriptContext::ReferenceError, "at(int i) const Invalid type");
//
//    return element_type();
//}
//
//Vec4fPrototype::element_type& Vec4fPrototype::at(int i)
//{
//    cpp_type *that = qscriptvalue_cast<cpp_type*>(thisObject());
//    if (that)
//        return (*that)[i];
//
//    context()->throwError(QScriptContext::ReferenceError, "at(int i) Invalid type");
//
//    return (*that)[0];
//}

//QString Vec4fPrototype::toString() const
//{
//    //return QString("nadaaar");
//
//    cpp_type *that = qscriptvalue_cast<cpp_type*>(thisObject());
//    if (!that)
//    {
//        context()->throwError(QScriptContext::ReferenceError, "toString() const Invalid type");
//
//        return QString();
//    }
//
//    qsreal n[4] = {(*that)[0], (*that)[1], (*that)[2], (*that)[3]};
//
//    QString result = QString::fromLatin1("Vec4(x: %1, y: %2, z: %3, w: %4)")
//        .arg(n[0]).arg(n[1]).arg(n[2]).arg(n[3]);
//
//    return result;
//}

//Vec4bPrototype::Vec4bPrototype(QObject *parent)
//: QObject(parent)
//{
//}
//
//Vec4bPrototype::element_type Vec4bPrototype::at(int i) const
//{
//    cpp_type *that = qscriptvalue_cast<cpp_type*>(thisObject());
//    if (that)
//        return (*that)[i];
//
//    context()->throwError(QScriptContext::ReferenceError, "Invalid type");
//
//    return element_type();
//}
//
//Vec4bPrototype::element_type& Vec4bPrototype::at(int i)
//{
//    cpp_type *that = qscriptvalue_cast<cpp_type*>(thisObject());
//    if (that)
//        return (*that)[i];
//
//    context()->throwError(QScriptContext::ReferenceError, "Invalid type");
//
//    return (*that)[0];
//}
//
//
//
//Vec3bPrototype::Vec3bPrototype(QObject *parent)
//: QObject(parent)
//{
//}
//
//Vec3bPrototype::element_type Vec3bPrototype::at(int i) const
//{
//    cpp_type *that = qscriptvalue_cast<cpp_type*>(thisObject());
//    if (that)
//        return (*that)[i];
//
//    context()->throwError(QScriptContext::ReferenceError, "Invalid type");
//
//    return element_type();
//}
//
//Vec3bPrototype::element_type& Vec3bPrototype::at(int i)
//{
//    cpp_type *that = qscriptvalue_cast<cpp_type*>(thisObject());
//    if (that)
//        return (*that)[i];
//
//    context()->throwError(QScriptContext::ReferenceError, "Invalid type");
//
//    return (*that)[0];
//}


CvMatrix::CvMatrix(const cv::Mat &mat, QObject *parent)
    : QObject(parent), m_mat(mat), m_pFnAt(0), m_pFnSetAt(0)
{
    getOptimizedFunctions(m_pFnAt, m_pFnSetAt, m_mat.depth(), m_mat.channels());
}

CvMatrix::CvMatrix(QObject *parent)
    : QObject(parent), m_pFnAt(0), m_pFnSetAt(0)
{

}

CvMatrix::CvMatrix(const CvMatrix & m)
    : QObject(m.parent()), m_mat(m.m_mat), m_pFnAt(m.m_pFnAt), m_pFnSetAt(m.m_pFnSetAt)
{
}

CvMatrix::CvMatrix(int _rows, int _cols, int _type)
    : m_mat(_rows, _cols, _type), m_pFnAt(0), m_pFnSetAt(0)
{
    getOptimizedFunctions(m_pFnAt, m_pFnSetAt, m_mat.depth(), m_mat.channels());
}

CvMatrix::~CvMatrix()
{

}

CvMatrix& CvMatrix::operator = (const CvMatrix& m) {
    this->setParent(m.parent());
    m_mat = m.m_mat;
    m_pFnAt = m.m_pFnAt;
    m_pFnSetAt = m.m_pFnSetAt;

    emit modified(*this);

    return *this;
}

CvMatrix& CvMatrix::operator = (const cv::Mat& m) {
    m_mat = m;
    getOptimizedFunctions(m_pFnAt, m_pFnSetAt, m_mat.depth(), m_mat.channels());

    emit modified(*this);

    return *this;
}

QCLImageFormat CvMatrix::getImageFormat(int depth, int channels)
{
    QCLImageFormat::ChannelType chType = QCLImageFormat::Type_Normalized_UInt8;
    QCLImageFormat::ChannelOrder chOrder = QCLImageFormat::Order_RGBA;

    switch(channels)
    {
    case 4:
        chOrder = QCLImageFormat::Order_RGBA;
        break;
    case 3:
        chOrder = QCLImageFormat::Order_RGB;
        break;
    case 2:
        chOrder = QCLImageFormat::Order_RG;
        break;
    case 1:
        chOrder = QCLImageFormat::Order_R;
        break;
    }

    switch(depth)
    {
    case CV_8U:
        chType = QCLImageFormat::Type_Normalized_UInt8;
        break;
    case CV_8S:
        chType = QCLImageFormat::Type_Normalized_Int8;
        break;
    case CV_16U:
        chType = QCLImageFormat::Type_Normalized_UInt16;
        break;
    case CV_16S:
        chType = QCLImageFormat::Type_Normalized_Int16;
        break;
    case CV_32S:
        chType = QCLImageFormat::Type_Unnormalized_Int32;
        break;
    case CV_32F:
        chType = QCLImageFormat::Type_Float;
        break;
    }

    return QCLImageFormat(chOrder, chType);
}

bool CvMatrix::fromQCLImageFormat(int &depth, int &channels, const QCLImageFormat & imgFormat)
{
    QCLImageFormat::ChannelType chType = imgFormat.channelType();
    QCLImageFormat::ChannelOrder chOrder = imgFormat.channelOrder();

    switch(chType)
    {
    case QCLImageFormat::Type_Normalized_UInt8:
    case QCLImageFormat::Type_Unnormalized_UInt8:
    case QCLImageFormat::Type_Normalized_565:
    case QCLImageFormat::Type_Normalized_555:
        depth = CV_8U;
        break;
    case QCLImageFormat::Type_Normalized_Int8:
    case QCLImageFormat::Type_Unnormalized_Int8:
        depth = CV_8S;
        break;
    case QCLImageFormat::Type_Normalized_UInt16:
    case QCLImageFormat::Type_Unnormalized_UInt16:
    case QCLImageFormat::Type_Normalized_101010:
        depth = CV_16U;
        break;
    case QCLImageFormat::Type_Normalized_Int16:
    case QCLImageFormat::Type_Unnormalized_Int16:
        depth = CV_16S;
        break;
    case QCLImageFormat::Type_Unnormalized_Int32:
    case QCLImageFormat::Type_Unnormalized_UInt32:
        depth = CV_32S;
        break;
    case QCLImageFormat::Type_Float:
    case QCLImageFormat::Type_Half_Float:
        depth = CV_32F;
        break;
    //case :
    //    depth = CV_64F;
    //    break;
    default:
        depth = CV_USRTYPE1;
    }

    switch(chOrder)
    {
    case QCLImageFormat::Order_R:
    case QCLImageFormat::Order_A:
    case QCLImageFormat::Order_Intensity:
    case QCLImageFormat::Order_Luminence:
        channels = 1;
        break;
    case QCLImageFormat::Order_RG:
    case QCLImageFormat::Order_RA:
    case QCLImageFormat::Order_Rx:
        channels = 2;
        break;
    case QCLImageFormat::Order_RGB:
    case QCLImageFormat::Order_RGx:
        channels = 3;
        break;
    case QCLImageFormat::Order_RGBA:
    case QCLImageFormat::Order_BGRA:
    case QCLImageFormat::Order_ARGB:
    case QCLImageFormat::Order_RGBx:
        channels = 4;
        break;
    default:
        channels = 0;
    }
    //QImage::Format fmt = imgFormat.toQImageFormat();

    return channels != 0 && depth != CV_USRTYPE1;
}



QString CvMatrix::toString() const
{
    return QString("CvMatrix(rows:%1, cols:%2, channels:%3)").arg(m_mat.rows).arg(m_mat.cols).arg(m_mat.channels());
}

template<typename T, int cn, typename Tret, int cnRet>
void CvMatrix::setAtType(int x, int y, const cv::Vec<Tret, cnRet> & vec)
{
    StaticAssert(cn <= cnRet && cn >= 0, ChannelCountError );

    typedef cv::Vec<T, cn> vec_t;
    vec_t & v = m_mat.at<vec_t>(x, y);

    for(int i = 0; i < cn; ++i)
        v[i] = (T) vec[i];
}

template<typename T, int cn, typename Tret, int cnRet>
cv::Vec<Tret, cnRet> CvMatrix::atType(int x, int y) const
{
    StaticAssert(cn <= cnRet && cn >= 0, ChannelCountError );

    cv::Vec<Tret, cnRet> vRet(cv::Vec<Tret, cnRet>::all(Tret())); //(0,0,0,0);

    typedef cv::Vec<T, cn> vec_t;
    const vec_t & v = m_mat.at<vec_t>(x, y);

    for(int i = 0; i < cn; ++i)
        vRet[i] = (Tret) v[i];

    return vRet;
}

void CvMatrix::getOptimizedFunctions(pFnAt_t & pFnAt, pFnSetAt_t & pFnSetAt, int depth, int channels)
{
    switch(depth)
    {
    case CV_8U:
        switch(channels)
        {
        case 3:
            pFnAt    = &CvMatrix::atType   <uchar, 3, float, 4>;  break;
            pFnSetAt = &CvMatrix::setAtType<uchar, 3, float, 4>;  break;
        case 4:
            pFnAt    = &CvMatrix::atType   <uchar, 4, float, 4>;  break;
            pFnSetAt = &CvMatrix::setAtType<uchar, 4, float, 4>;  break;
        case 2:
            pFnAt    = &CvMatrix::atType   <uchar, 2, float, 4>;  break;
            pFnSetAt = &CvMatrix::setAtType<uchar, 2, float, 4>;  break;
        case 1:
            pFnAt    = &CvMatrix::atType   <uchar, 1, float, 4>;  break;
            pFnSetAt = &CvMatrix::setAtType<uchar, 1, float, 4>;  break;
        }
        break;
    case CV_32F:
        switch(channels)
        {
        case 3:
            pFnAt    = &CvMatrix::atType   <float, 3, float, 4>;  break;
            pFnSetAt = &CvMatrix::setAtType<float, 3, float, 4>;  break;
        case 4:
            pFnAt    = &CvMatrix::atType   <float, 4, float, 4>;  break;
            pFnSetAt = &CvMatrix::setAtType<float, 4, float, 4>;  break;
        case 2:
            pFnAt    = &CvMatrix::atType   <float, 2, float, 4>;  break;
            pFnSetAt = &CvMatrix::setAtType<float, 2, float, 4>;  break;
        case 1:
            pFnAt    = &CvMatrix::atType   <float, 1, float, 4>;  break;
            pFnSetAt = &CvMatrix::setAtType<float, 1, float, 4>;  break;
        }
        break;
    }
}

cv::Vec4f CvMatrix::at(int x, int y) const
{
    return (this->*m_pFnAt)(x, y);
}

void CvMatrix::setAt(int x, int y, const cv::Vec4f & vec) 
{
    return (this->*m_pFnSetAt)(x, y, vec);
}

cv::Vec4f CvMatrix::atDynamic(int x, int y) const
{
    cv::Vec4f vRet;
    int channels = m_mat.channels();
    int depth = m_mat.depth();

    switch(depth)
    {
    case CV_8U:
        switch(channels)
        {
        case 3:
            vRet = atType<uchar, 3, float, 4>(x, y); break;
        case 4:
            vRet = atType<uchar, 4, float, 4>(x, y); break;
        case 2:
            vRet = atType<uchar, 2, float, 4>(x, y); break;
        case 1:
            vRet = atType<uchar, 1, float, 4>(x, y); break;
        }
        break;
    case CV_32F:
        switch(channels)
        {
        case 3:
            vRet = atType<float, 3, float, 4>(x, y); break;
        case 4:
            vRet = atType<float, 4, float, 4>(x, y); break;
        case 2:
            vRet = atType<float, 2, float, 4>(x, y); break;
        case 1:
            vRet = atType<float, 1, float, 4>(x, y); break;
        }
        break;
    }

    return vRet;
}

     
     
